package org.gdstash.db;

import org.gdstash.file.ARZRecord;
import org.gdstash.item.GDItem;
import org.gdstash.util.GDMsgFormatter;
import org.gdstash.util.GDMsgLogger;

import java.sql.*;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class DBLootTable {
    private static final String TABLE_NAME = "GD_LOOTTABLE_HD";
    private static final int ROW_TABLE_ID = 1;
    private static final int ROW_NPRE_NSUF = 2;
    private static final int ROW_NPRE_RSUF = 3;
    private static final int ROW_RPRE_NSUF = 4;
    private static final int ROW_RPRE_RSUF = 5;
    private static ConcurrentHashMap<String, DBLootTable> hashBuffer;
    private String tableID;
    private boolean nPre_nSuf;
    private boolean nPre_rSuf;
    private boolean rPre_nSuf;
    private boolean rPre_rSuf;
    private List<DBLootTableItemAlloc> items;
    private List<DBLootTableAffixSetAlloc> affixSets;
    private List<DBAffix> magicPrefixes;
    private List<DBAffix> rarePrefixes;
    private List<DBAffix> magicSuffixes;
    private List<DBAffix> rareSuffixes;
    private List<String> itemIDs;

    public DBLootTable() {
        this.items = new LinkedList<DBLootTableItemAlloc>();
        this.affixSets = new LinkedList<DBLootTableAffixSetAlloc>();
    }

    private DBLootTable(final ARZRecord record) {
        this.tableID = record.getFileName();
        this.nPre_nSuf = record.getTableNormalPrefixSuffix();
        this.nPre_rSuf = record.getTableNormalPrefixRareSuffix();
        this.rPre_nSuf = record.getTableRarePrefixNormalSuffix();
        this.rPre_rSuf = record.getTableRarePrefixSuffix();
        this.items = record.getTableItemAllocList();
        this.affixSets = record.getTableAffixSetAllocList();
    }

    static {
        DBLootTable.hashBuffer = new ConcurrentHashMap<String, DBLootTable>();
    }

    public static void mixAffixes(final GDItem item, final List<DBAffix> allAffixes, final List<DBAffix> addAffixes) {
        for (final DBAffix affix : addAffixes) {
            if (item != null && (item.getRarity().equals("Epic") || item.getRarity().equals("Legendary")) && !item.getRarity().equals(affix.getRarity())) {
                continue;
            }
            if (allAffixes.contains(affix)) {
                continue;
            }
            allAffixes.add(affix);
        }
    }

    public String getTableID() {
        return this.tableID;
    }

    public boolean isMagicPrefixMagicSuffixAllowed() {
        return this.nPre_nSuf;
    }

    public boolean isMagicPrefixRareSuffixAllowed() {
        return this.nPre_rSuf;
    }

    public boolean isRarePrefixMagicSuffixAllowed() {
        return this.rPre_nSuf;
    }

    public boolean isRarePrefixRareSuffixAllowed() {
        return this.rPre_rSuf;
    }

    public List<DBLootTableItemAlloc> getItemAllocList() {
        return this.items;
    }

    public List<DBLootTableAffixSetAlloc> getAffixSetAllocList() {
        return this.affixSets;
    }

    public static void clearBuffer() {
        DBLootTable.hashBuffer.clear();
    }

    public static void createTables() throws SQLException {
        final String dropTable = "DROP TABLE GD_LOOTTABLE_HD";
        final String createTable = "CREATE TABLE GD_LOOTTABLE_HD (TABLE_ID    VARCHAR(256) NOT NULL, NPRE_NSUF   BOOLEAN, NPRE_RSUF   BOOLEAN, RPRE_NSUF   BOOLEAN, RPRE_RSUF   BOOLEAN, PRIMARY KEY (TABLE_ID))";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final Statement st = conn.createStatement()) {
                if (GDDBUtil.tableExists(conn, "GD_LOOTTABLE_HD")) {
                    st.execute(dropTable);
                }
                st.execute(createTable);
                st.close();
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {"GD_LOOTTABLE_HD"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_CREATE_TABLE", args);
                GDMsgLogger.addError(msg);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        }
        DBLootTableItemAlloc.createTable();
        DBLootTableAffixSetAlloc.createTable();
    }

    public static void delete(final String tableID) throws SQLException {
        final String deleteEntry = "DELETE FROM GD_LOOTTABLE_HD WHERE TABLE_ID = ?";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(deleteEntry)) {
                ps.setString(1, tableID);
                ps.executeUpdate();
                ps.close();
                DBLootTableItemAlloc.delete(conn, tableID);
                DBLootTableAffixSetAlloc.delete(conn, tableID);
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {tableID, "GD_LOOTTABLE_HD"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_DEL_TABLE_BY_ID", args);
                GDMsgLogger.addError(msg);
                GDMsgLogger.addError(ex);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        } catch (SQLException ex2) {
            throw ex2;
        }
    }

    public static void insert(final ARZRecord record) throws SQLException {
        final DBLootTable entry = get(record.getFileName());
        if (entry != null) {
            return;
        }
        final DBLootTable table = new DBLootTable(record);
        if (table.items == null) {
            return;
        }
        if (table.items.isEmpty()) {
            return;
        }
        if (table.affixSets == null) {
            return;
        }
        if (table.affixSets.isEmpty()) {
            return;
        }
        final String insert = "INSERT INTO GD_LOOTTABLE_HD VALUES (?,?,?,?,?)";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(insert)) {
                ps.setString(1, table.tableID);
                ps.setBoolean(2, table.nPre_nSuf);
                ps.setBoolean(3, table.nPre_rSuf);
                ps.setBoolean(4, table.rPre_nSuf);
                ps.setBoolean(5, table.rPre_rSuf);
                ps.executeUpdate();
                ps.close();
                conn.commit();
                DBLootTableItemAlloc.insert(conn, table);
                DBLootTableAffixSetAlloc.insert(conn, table);
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {record.getFileName(), "GD_LOOTTABLE_HD"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_INS_TABLE_BY_ID", args);
                GDMsgLogger.addLowError(msg);
                GDMsgLogger.addLowError(ex);
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static DBLootTable get(final String tableID) {
        DBLootTable table = null;
        table = DBLootTable.hashBuffer.get(tableID);
        if (table == null) {
            table = getDB(tableID);
            if (table != null) {
                DBLootTable.hashBuffer.put(table.tableID, table);
            }
        }
        return table;
    }

    private static DBLootTable getDB(final String tableID) {
        DBLootTable table = null;
        final String command = "SELECT * FROM GD_LOOTTABLE_HD WHERE TABLE_ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, tableID);
            try (final ResultSet rs = ps.executeQuery()) {
                final List<DBLootTable> list = wrap(rs);
                if (list.isEmpty()) {
                    table = null;
                } else {
                    table = list.get(0);
                }
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {tableID, "GD_LOOTTABLE_HD"};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return table;
    }

    public static List<DBLootTable> getByItemID(final String itemID) {
        final List<DBLootTable> list = new LinkedList<DBLootTable>();
        List<DBLootTableItemAlloc> items = null;
        items = DBLootTableItemAlloc.getByItemID(itemID);
        if (items == null) {
            return list;
        }
        if (items.isEmpty()) {
            return list;
        }
        for (final DBLootTableItemAlloc item : items) {
            final DBLootTable table = get(item.getTableID());
            if (table != null) {
                list.add(table);
            }
        }
        return list;
    }

    private static List<DBLootTable> wrap(final ResultSet rs) throws SQLException {
        final LinkedList<DBLootTable> list = new LinkedList<DBLootTable>();
        final boolean found = false;
        while (rs.next()) {
            final DBLootTable table = new DBLootTable();
            table.tableID = rs.getString(1);
            table.nPre_nSuf = rs.getBoolean(2);
            table.nPre_rSuf = rs.getBoolean(3);
            table.rPre_nSuf = rs.getBoolean(4);
            table.rPre_rSuf = rs.getBoolean(5);
            table.items = DBLootTableItemAlloc.getByTableID(table.tableID);
            table.affixSets = DBLootTableAffixSetAlloc.getByTableID(table.tableID);
            table.groupAffixes();
            list.add(table);
        }
        return list;
    }

    public List<DBAffix> getAllAffixes() {
        final List<DBAffix> list = new LinkedList<DBAffix>();
        for (final DBAffix affix : this.rarePrefixes) {
            list.add(affix);
        }
        for (final DBAffix affix : this.magicPrefixes) {
            list.add(affix);
        }
        for (final DBAffix affix : this.rareSuffixes) {
            list.add(affix);
        }
        for (final DBAffix affix : this.magicSuffixes) {
            list.add(affix);
        }
        return list;
    }

    public List<DBAffix> getMagicPrefixes() {
        return this.magicPrefixes;
    }

    public List<DBAffix> getRarePrefixes() {
        return this.rarePrefixes;
    }

    public List<DBAffix> getMagicSuffixes() {
        return this.magicSuffixes;
    }

    public List<DBAffix> getRareSuffixes() {
        return this.rareSuffixes;
    }

    public List<DBAffix> getFilterPrefixesForSuffix(final DBAffix suffix, final List<DBAffix> filterPrefixes) {
        final List<DBAffix> filter = new LinkedList<DBAffix>();
        if (suffix == null) {
            for (final DBAffix affix : this.rarePrefixes) {
                if (filterPrefixes.contains(affix)) {
                    filter.add(affix);
                }
            }
            for (final DBAffix affix : this.magicPrefixes) {
                if (filterPrefixes.contains(affix)) {
                    filter.add(affix);
                }
            }
            return filter;
        }
        if (suffix.getAffixType() != 2) {
            return filter;
        }
        if ("Rare".equals(suffix.getRarity())) {
            if (!this.rareSuffixes.contains(suffix)) {
                return filter;
            }
            if (this.rPre_rSuf) {
                for (final DBAffix affix : this.rarePrefixes) {
                    if (filterPrefixes.contains(affix)) {
                        filter.add(affix);
                    }
                }
            }
            if (this.nPre_rSuf) {
                for (final DBAffix affix : this.magicPrefixes) {
                    if (filterPrefixes.contains(affix)) {
                        filter.add(affix);
                    }
                }
            }
        } else {
            if (!this.magicSuffixes.contains(suffix)) {
                return filter;
            }
            if (this.rPre_nSuf) {
                for (final DBAffix affix : this.rarePrefixes) {
                    if (filterPrefixes.contains(affix)) {
                        filter.add(affix);
                    }
                }
            }
            if (this.nPre_nSuf) {
                for (final DBAffix affix : this.magicPrefixes) {
                    if (filterPrefixes.contains(affix)) {
                        filter.add(affix);
                    }
                }
            }
        }
        return filter;
    }

    public List<DBAffix> getFilterSuffixesForPrefix(final DBAffix prefix, final List<DBAffix> filterSuffixes) {
        final List<DBAffix> filter = new LinkedList<DBAffix>();
        if (prefix == null) {
            for (final DBAffix affix : this.rareSuffixes) {
                if (filterSuffixes.contains(affix)) {
                    filter.add(affix);
                }
            }
            for (final DBAffix affix : this.magicSuffixes) {
                if (filterSuffixes.contains(affix)) {
                    filter.add(affix);
                }
            }
            return filter;
        }
        if (prefix.getAffixType() != 1) {
            return filter;
        }
        if ("Rare".equals(prefix.getRarity())) {
            if (!this.rarePrefixes.contains(prefix)) {
                return filter;
            }
            if (this.rPre_rSuf) {
                for (final DBAffix affix : this.rareSuffixes) {
                    if (filterSuffixes.contains(affix)) {
                        filter.add(affix);
                    }
                }
            }
            if (this.rPre_nSuf) {
                for (final DBAffix affix : this.magicSuffixes) {
                    if (filterSuffixes.contains(affix)) {
                        filter.add(affix);
                    }
                }
            }
        } else {
            if (!this.magicPrefixes.contains(prefix)) {
                return filter;
            }
            if (this.nPre_rSuf) {
                for (final DBAffix affix : this.rareSuffixes) {
                    if (filterSuffixes.contains(affix)) {
                        filter.add(affix);
                    }
                }
            }
            if (this.nPre_nSuf) {
                for (final DBAffix affix : this.magicSuffixes) {
                    if (filterSuffixes.contains(affix)) {
                        filter.add(affix);
                    }
                }
            }
        }
        return filter;
    }

    public boolean containsItem(final DBItem item) {
        return this.itemIDs.contains(item.getItemID());
    }

    public List<String> getItemIDs() {
        final List<String> list = new LinkedList<String>();
        if (this.items == null) {
            return list;
        }
        for (final DBLootTableItemAlloc item : this.items) {
            if (item != null && item.getItemID() != null) {
                list.add(item.getItemID());
            }
        }
        return list;
    }

    private void groupAffixes() {
        this.magicPrefixes = new LinkedList<DBAffix>();
        this.rarePrefixes = new LinkedList<DBAffix>();
        this.magicSuffixes = new LinkedList<DBAffix>();
        this.rareSuffixes = new LinkedList<DBAffix>();
        this.itemIDs = this.getItemIDs();
        for (final DBLootTableAffixSetAlloc as : this.affixSets) {
            final List<DBAffixSet.DBEntry> asEntries = as.getAffixEntries();
            if (asEntries == null) {
                continue;
            }
            for (final DBAffixSet.DBEntry asEntry : asEntries) {
                if (asEntry == null) {
                    continue;
                }
                final String affixID = asEntry.getAffixID();
                if (affixID == null) {
                    continue;
                }
                final DBAffix affix = DBAffix.get(affixID);
                if (affix == null) {
                    continue;
                }
                final String rarity = affix.getRarity();
                final int type = affix.getAffixType();
                if (rarity == null) {
                    continue;
                }
                if (type == 1) {
                    if (rarity.equals("Magical") && !this.magicPrefixes.contains(affix)) {
                        this.magicPrefixes.add(affix);
                    }
                    if (rarity.equals("Rare") && !this.rarePrefixes.contains(affix)) {
                        this.rarePrefixes.add(affix);
                    }
                    if (rarity.equals("Epic") && !this.magicPrefixes.contains(affix)) {
                        this.magicPrefixes.add(affix);
                    }
                    if (rarity.equals("Legendary") && !this.magicPrefixes.contains(affix)) {
                        this.magicPrefixes.add(affix);
                    }
                }
                if (type != 2) {
                    continue;
                }
                if (rarity.equals("Magical") && !this.magicSuffixes.contains(affix)) {
                    this.magicSuffixes.add(affix);
                }
                if (rarity.equals("Rare") && !this.rareSuffixes.contains(affix)) {
                    this.rareSuffixes.add(affix);
                }
                if (rarity.equals("Epic") && !this.magicSuffixes.contains(affix)) {
                    this.magicSuffixes.add(affix);
                }
                if (!rarity.equals("Legendary") || this.magicSuffixes.contains(affix)) {
                    continue;
                }
                this.magicSuffixes.add(affix);
            }
        }
    }

    public List<String> getAffixSetIDs() {
        final List<String> list = new LinkedList<String>();
        if (this.affixSets == null) {
            return list;
        }
        for (final DBLootTableAffixSetAlloc set : this.affixSets) {
            if (set != null && set.getAffixSetID() != null) {
                list.add(set.getAffixSetID());
            }
        }
        return list;
    }
}
